using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;

namespace Common {
	public class ComplexValue {
		#region Ctors
		public ComplexValue(bool value) {
			CurrentType = Type.Bool;
			RawValue = value;
		}
		public ComplexValue(byte value) {
			CurrentType = Type.Byte;
			RawValue = value;
		}
		public ComplexValue(sbyte value) {
			CurrentType = Type.SByte;
			RawValue = value;
		}
		public ComplexValue(short value) {
			CurrentType = Type.Short;
			RawValue = value;
		}
		public ComplexValue(ushort value) {
			CurrentType = Type.UShort;
			RawValue = value;
		}
		public ComplexValue(int value) {
			CurrentType = Type.Int;
			RawValue = value;
		}
		public ComplexValue(uint value) {
			CurrentType = Type.UInt;
			RawValue = value;
		}
		public ComplexValue(long value) {
			CurrentType = Type.Long;
			RawValue = value;
		}
		public ComplexValue(ulong value) {
			CurrentType = Type.ULong;
			RawValue = value;
		}
		public ComplexValue(float value) {
			CurrentType = Type.Float;
			RawValue = value;
		}
		public ComplexValue(double value) {
			CurrentType = Type.Double;
			RawValue = value;
		}
		public ComplexValue(string value) {
			CurrentType = Type.String;
			RawValue = value;
		}
		ComplexValue(Type type, object value) {
			CurrentType = type;
			RawValue = value;
		}
		#endregion

		#region Implicit ctors
		public static implicit operator ComplexValue(bool value) => new ComplexValue(value);
		public static implicit operator ComplexValue(byte value) => new ComplexValue(value);
		public static implicit operator ComplexValue(sbyte value) => new ComplexValue(value);
		public static implicit operator ComplexValue(short value) => new ComplexValue(value);
		public static implicit operator ComplexValue(ushort value) => new ComplexValue(value);
		public static implicit operator ComplexValue(int value) => new ComplexValue(value);
		public static implicit operator ComplexValue(uint value) => new ComplexValue(value);
		public static implicit operator ComplexValue(long value) => new ComplexValue(value);
		public static implicit operator ComplexValue(ulong value) => new ComplexValue(value);
		public static implicit operator ComplexValue(float value) => new ComplexValue(value);
		public static implicit operator ComplexValue(double value) => new ComplexValue(value);
		public static implicit operator ComplexValue(string value) => new ComplexValue(value);
		#endregion

		/// <summary>
		/// The ComplexValue represents Null.
		/// </summary>
		public static ComplexValue Null => new ComplexValue(Type.Null, null);

		/// <summary>
		/// Available type that the values can be.
		/// </summary>
		public enum Type : byte {
			Null,
			Bool,
			Byte,
			SByte,
			Short,
			UShort,
			Int,
			UInt,
			Long,
			ULong,
			Float,
			Double,
			String,
			List,
			Dictionary
		}

		/// <summary>
		/// Current type of the current value; 
		/// </summary>
		/// <value></value>
		public Type CurrentType { get; }

		/// <summary>
		/// The .NET raw object of the current value;
		/// </summary>
		public object RawValue { get; }

		#region Is As s
		/// <summary>
		/// Whether the current value is Null.
		/// </summary>
		public bool IsNull => CurrentType == Type.Null;

		/// <summary>
		/// Whether the current value is a Boolean.
		/// </summary>
		public bool IsBool => CurrentType == Type.Bool;

		/// <summary>
		/// Try to get the current value as Boolean.
		/// </summary>
		/// <param name="defaultValue">The default value returned when the convertion fails</param>
		/// <returns>Current value as Boolean or the given default value</returns>
		public bool AsBool(bool defaultValue = false) {
			if (IsNull) {
				return defaultValue;
			}
			try {
				return Convert.ToBoolean(RawValue);
			} catch (InvalidCastException) {
				return defaultValue;
			}
		}

		/// <summary>
		/// Whether the current value is a Unsigned Byte.
		/// </summary>
		public bool IsByte => CurrentType == Type.Byte;

		/// <summary>
		/// Try to get the current value as Unsigned Byte.
		/// </summary>
		/// <param name="defaultValue">The default value returned when the convertion fails</param>
		/// <returns>Current value as Unsigned Byte or the given default value</returns>
		public byte AsByte(byte defaultValue = 0) {
			if (IsNull) {
				return defaultValue;
			}
			try {
				return Convert.ToByte(RawValue);
			} catch (InvalidCastException) {
				return defaultValue;
			}
		}

		/// <summary>
		/// Whether the current value is a Signed Byte.
		/// </summary>
		public bool IsSByte => CurrentType == Type.SByte;

		/// <summary>
		/// Try to get the current value as Signed Byte.
		/// </summary>
		/// <param name="defaultValue">The default value returned when the convertion fails</param>
		/// <returns>Current value as Signed Byte or the given default value</returns>
		public sbyte AsSByte(sbyte defaultValue = 0) {
			if (IsNull) {
				return defaultValue;
			}
			try {
				return Convert.ToSByte(RawValue);
			} catch (InvalidCastException) {
				return defaultValue;
			}
		}

		/// <summary>
		/// Whether the current value is a Signed 16-bit Integer.
		/// </summary>
		public bool IsShort => CurrentType == Type.Short;

		/// <summary>
		/// Try to get the current value as Signed 16-bit Integer.
		/// </summary>
		/// <param name="defaultValue">The default value returned when the convertion fails</param>
		/// <returns>Current value as Signed 16-bit Integer or the given default value</returns>
		public short AsShort(short defaultValue = 0) {
			if (IsNull) {
				return defaultValue;
			}
			try {
				return Convert.ToInt16(RawValue);
			} catch (InvalidCastException) {
				return defaultValue;
			}
		}

		/// <summary>
		/// Whether the current value is a Unsigned 16-bit Integer.
		/// </summary>
		public bool IsUShort => CurrentType == Type.UShort;

		/// <summary>
		/// Try to get the current value as Unsigned 16-bit Integer.
		/// </summary>
		/// <param name="defaultValue">The default value returned when the convertion fails</param>
		/// <returns>Current value as Unsigned 16-bit Integer or the given default value</returns>
		public ushort AsUShort(ushort defaultValue = 0) {
			if (IsNull) {
				return defaultValue;
			}
			try {
				return Convert.ToUInt16(RawValue);
			} catch (InvalidCastException) {
				return defaultValue;
			}
		}

		/// <summary>
		/// Whether the current value is a Signed 32-bit Integer.
		/// </summary>
		public bool IsInt => CurrentType == Type.Int;

		/// <summary>
		/// Try to get the current value as Signed 32-bit Integer.
		/// </summary>
		/// <param name="defaultValue">The default value returned when the convertion fails</param>
		/// <returns>Current value as Signed 32-bit Integer or the given default value</returns>
		public int AsInt(int defaultValue = 0) {
			if (IsNull) {
				return defaultValue;
			}
			try {
				return Convert.ToInt32(RawValue);
			} catch (InvalidCastException) {
				return defaultValue;
			}
		}

		/// <summary>
		/// Whether the current value is a Unsigned 32-bit Integer.
		/// </summary>
		public bool IsUInt => CurrentType == Type.UInt;

		/// <summary>
		/// Try to get the current value as Unsigned 32-bit Integer.
		/// </summary>
		/// <param name="defaultValue">The default value returned when the convertion fails</param>
		/// <returns>Current value as Unsigned 32-bit Integer or the given default value</returns>
		public uint AsUInt(uint defaultValue = 0) {
			if (IsNull) {
				return defaultValue;
			}
			try {
				return Convert.ToUInt32(RawValue);
			} catch (InvalidCastException) {
				return defaultValue;
			}
		}

		/// <summary>
		/// Whether the current value is a Signed 64-bit Integer.
		/// </summary>
		public bool IsLong => CurrentType == Type.Long;

		/// <summary>
		/// Try to get the current value as Signed 64-bit Integer.
		/// </summary>
		/// <param name="defaultValue">The default value returned when the convertion fails</param>
		/// <returns>Current value as Signed 64-bit Integer or the given default value</returns>
		public long AsLong(long defaultValue = 0) {
			if (IsNull) {
				return defaultValue;
			}
			try {
				return Convert.ToInt64(RawValue);
			} catch (InvalidCastException) {
				return defaultValue;
			}
		}

		/// <summary>
		/// Whether the current value is a Unsigned 64-bit Integer.
		/// </summary>
		public bool IsULong => CurrentType == Type.ULong;

		/// <summary>
		/// Try to get the current value as Unsigned 64-bit Integer.
		/// </summary>
		/// <param name="defaultValue">The default value returned when the convertion fails</param>
		/// <returns>Current value as Unsigned 64-bit Integer or the given default value</returns>
		public ulong AsULong(ulong defaultValue = 0) {
			if (IsNull) {
				return defaultValue;
			}
			try {
				return Convert.ToUInt64(RawValue);
			} catch (InvalidCastException) {
				return defaultValue;
			}
		}

		/// <summary>
		/// Whether the current value is a Single Float.
		/// </summary>
		public bool IsFloat => CurrentType == Type.Float;

		/// <summary>
		/// Try to get the current value as Single Float.
		/// </summary>
		/// <param name="defaultValue">The default value returned when the convertion fails</param>
		/// <returns>Current value as Single Float or the given default value</returns>
		public float AsFloat(float defaultValue = 0) {
			if (IsNull) {
				return defaultValue;
			}
			try {
				return Convert.ToSingle(RawValue);
			} catch (InvalidCastException) {
				return defaultValue;
			}
		}

		/// <summary>
		/// Whether the current value is a Double Float.
		/// </summary>
		public bool IsDouble => CurrentType == Type.Double;

		/// <summary>
		/// Try to get the current value as Double Float.
		/// </summary>
		/// <param name="defaultValue">The default value returned when the convertion fails</param>
		/// <returns>Current value as Double Float or the given default value</returns>
		public double AsDouble(double defaultValue = 0) {
			if (IsNull) {
				return defaultValue;
			}
			try {
				return Convert.ToDouble(RawValue);
			} catch (InvalidCastException) {
				return defaultValue;
			}
		}

		/// <summary>
		/// Whether the current value is a String.
		/// </summary>
		public bool IsString => CurrentType == Type.String;

		/// <summary>
		/// Try to get the current value as String.
		/// </summary>
		/// <returns>Current value as String</returns>
		public string AsString() => IsNull ? "Null" : RawValue.ToString();

		/// <summary>
		/// Whether the current value is a List.
		/// </summary>
		public bool IsList => CurrentType == Type.List;

		/// <summary>
		/// Try to get the current value as List.
		/// </summary>
		/// <param name="defaultValue">The default value returned when the convertion fails</param>
		/// <returns>Current value as List or the given default value</returns>
		public List AsList(List defaultValue = null) => IsList ? (List)this : defaultValue;

		/// <summary>
		/// Whether the current value is a Dictionary.
		/// </summary>
		public bool IsDictionary => CurrentType == Type.Dictionary;

		/// <summary>
		/// Try to get the current value as Dictionary.
		/// </summary>
		/// <param name="defaultValue">The default value returned when the convertion fails</param>
		/// <returns>Current value as Dictionary or the given default value</returns>
		public Dictionary AsDictionary(Dictionary defaultValue = null) => IsDictionary ? (Dictionary)this : defaultValue;
		#endregion

		/// <summary>
		/// Get the current value as String.
		/// </summary>
		/// <returns>Current value as String</returns>
		public override string ToString() => AsString();

		/// <summary>
		/// Serialize current value to binary.
		/// </summary>
		/// <returns>Serialized byte array.</returns>
		public byte[] SerializeBinary() {
			byte[] result;
			MemoryStream ms = new MemoryStream();
			BinaryWriter bw = new BinaryWriter(ms);
			try {
				switch (CurrentType) {
					case Type.Null:
						bw.Write((byte)CurrentType);
						break;
					case Type.Bool:
						bw.Write((byte)CurrentType);
						bw.Write(AsBool());
						break;
					case Type.Byte:
						bw.Write((byte)CurrentType);
						bw.Write(AsByte());
						break;
					case Type.SByte:
						bw.Write((byte)CurrentType);
						bw.Write(AsSByte());
						break;
					case Type.Short:
						bw.Write((byte)CurrentType);
						bw.Write(AsShort());
						break;
					case Type.UShort:
						bw.Write((byte)CurrentType);
						bw.Write(AsUShort());
						break;
					case Type.Int:
						bw.Write((byte)CurrentType);
						bw.Write(AsInt());
						break;
					case Type.UInt:
						bw.Write((byte)CurrentType);
						bw.Write(AsUInt());
						break;
					case Type.Long:
						bw.Write((byte)CurrentType);
						bw.Write(AsLong());
						break;
					case Type.ULong:
						bw.Write((byte)CurrentType);
						bw.Write(AsULong());
						break;
					case Type.Float:
						bw.Write((byte)CurrentType);
						bw.Write(AsFloat());
						break;
					case Type.Double:
						bw.Write((byte)CurrentType);
						bw.Write(AsDouble());
						break;
					case Type.String:
						bw.Write((byte)CurrentType);
						bw.Write(AsString());
						break;
					case Type.List:
						bw.Write((byte)CurrentType);
						List list = AsList();
						bw.Write(list.Count);
						foreach (ComplexValue value in list) {
							bw.Write(value.SerializeBinary());
						}
						break;
					case Type.Dictionary:
						bw.Write((byte)CurrentType);
						Dictionary dic = AsDictionary();
						bw.Write(dic.Count);
						foreach (KeyValuePair<string, ComplexValue> pair in dic) {
							bw.Write(pair.Key);
							bw.Write(pair.Value.SerializeBinary());
						}
						break;
				}
			} finally {
				bw.Flush();
				result = ms.ToArray();
				bw.Close();
				ms.Close();
			}
			return result;
		}

		/// <summary>
		/// Deserialize given byte array to a ComplexValue.
		/// </summary>
		/// <param name="data">Byte array to deserialize</param>
		/// <param name="offset">The position where the deserialization starts</param>
		/// <param name="result">The deserialized ComplexValue</param>
		/// <returns>Number of bytes the method has deserialized</returns>
		public static int DeserializeBinary(byte[] data, int offset, out ComplexValue result) {
			if (offset < 0 || offset >= data.Length) {
				result = null;
				return 0;
			}

			int size = 0;
			MemoryStream ms = new MemoryStream(data, offset, data.Length - offset);
			BinaryReader br = new BinaryReader(ms);
			try {
				Type type = (Type)br.ReadByte();

				switch (type) {
					case Type.Null:
						result = Null;
						break;
					case Type.Bool:
						result = br.ReadBoolean();
						break;
					case Type.Byte:
						result = br.ReadByte();
						break;
					case Type.SByte:
						result = br.ReadSByte();
						break;
					case Type.Short:
						result = br.ReadInt16();
						break;
					case Type.UShort:
						result = br.ReadUInt16();
						break;
					case Type.Int:
						result = br.ReadInt32();
						break;
					case Type.UInt:
						result = br.ReadUInt32();
						break;
					case Type.Long:
						result = br.ReadInt64();
						break;
					case Type.ULong:
						result = br.ReadUInt64();
						break;
					case Type.Float:
						result = br.ReadSingle();
						break;
					case Type.Double:
						result = br.ReadDouble();
						break;
					case Type.String:
						result = br.ReadString();
						break;
					case Type.List:
						int count = br.ReadInt32();

						List list = new List();
						result = list;

						for (int i = 0; i < count; i += 1) {
							int processedSize = DeserializeBinary(ms.ToArray(), (int)ms.Position, out ComplexValue value);
							if (processedSize <= 0) {
								throw new InvalidDataException("Error when deserializing a list.");
							}
							ms.Seek(processedSize, SeekOrigin.Current);
							list.Add(value);
						}
						break;
					case Type.Dictionary:
						int count2 = br.ReadInt32();

						Dictionary dic = new Dictionary();
						result = dic;

						for (int i = 0; i < count2; i += 1) {
							string key = br.ReadString();

							int processedSize = DeserializeBinary(ms.ToArray(), (int)ms.Position, out ComplexValue value);
							if (processedSize <= 0) {
								throw new InvalidDataException("Error when deserializing a dictionary.");
							}
							ms.Seek(processedSize, SeekOrigin.Current);
							dic.Add(key, value);
						}
						break;
					default:
						throw new InvalidDataException(string.Format("Unknown type {0}.", type));
				}
				size = (int)ms.Position;
			} catch {
				result = null;
				size = 0;
			} finally {
				br.Close();
				ms.Close();
			}
			return size;
		}

		/// <summary>
		/// The sub-type of ComplexValue that represents a List.
		/// </summary>
		public class List : ComplexValue, IList<ComplexValue> {
			public List() : base(Type.List, new List<ComplexValue>()) {
				RawValue = base.RawValue as List<ComplexValue>;
			}
			public List(List<ComplexValue> value) : base(Type.List, value) {
				RawValue = value;
			}

			public new List<ComplexValue> RawValue { get; }

			/// <summary>
			/// Get the current List as String.
			/// </summary>
			/// <returns>Current List as String</returns>
			public override string ToString() => RawValue.ToString();

			#region IList interface
			public ComplexValue this[int index] {
				get => (index >= 0 && index < Count) ? RawValue[index] : Null;
				set => RawValue[index] = value;
			}
			public int Count => RawValue.Count;
			public bool IsReadOnly => false;
			public void Add(ComplexValue item) => RawValue.Add(item);
			public void Clear() => RawValue.Clear();
			public bool Contains(ComplexValue item) => RawValue.Contains(item);
			public void CopyTo(ComplexValue[] array, int arrayIndex) => RawValue.CopyTo(array, arrayIndex);
			public int IndexOf(ComplexValue item) => RawValue.IndexOf(item);
			public void Insert(int index, ComplexValue item) => RawValue.Insert(index, item);
			public bool Remove(ComplexValue item) => RawValue.Remove(item);
			public void RemoveAt(int index) => RawValue.RemoveAt(index);
			public IEnumerator<ComplexValue> GetEnumerator() => RawValue.GetEnumerator();
			IEnumerator IEnumerable.GetEnumerator() => RawValue.GetEnumerator();
			#endregion
		}

		/// <summary>
		/// The sub-type of ComplexValue that represents a Dictionary.
		/// </summary>
		public class Dictionary : ComplexValue, IDictionary<string, ComplexValue> {
			public Dictionary() : base(Type.Dictionary, new Dictionary<string, ComplexValue>()) {
				RawValue = base.RawValue as Dictionary<string, ComplexValue>;
			}
			public Dictionary(Dictionary<string, ComplexValue> value) : base(Type.Dictionary, value) {
				RawValue = value;
			}

			public new Dictionary<string, ComplexValue> RawValue { get; }

			/// <summary>
			/// Get or create a dictionary in this <see cref="Dictionary"/>.
			/// Non-<see cref="Dictionary"/> values will be overwritten.
			/// </summary>
			/// <param name="key">The key.</param>
			/// <param name="actionWhenCreate">The action to do when the dictionary is not found and a new one is created.</param>
			/// <returns>
			/// <see cref="Dictionary"/> when found.
			/// An empty <see cref="Dictionary"/> when not found.
			/// </returns>
			public Dictionary GetOrCreateDictionary(string key, Action<Dictionary> actionWhenCreate = null) {
				if (ContainsKey(key) && this[key].IsDictionary) {
					return this[key].AsDictionary();
				}
				Dictionary dic = new Dictionary();
				this[key] = dic;
				actionWhenCreate?.Invoke(dic);
				return dic;
			}

			/// <summary>
			/// Get or create a list in this <see cref="Dictionary"/>.
			/// Non-<see cref="List"/> values will be overwritten.
			/// </summary>
			/// <param name="key">The key.</param>
			/// <param name="actionWhenCreate">The action to do when the list is not found and a new one is created.</param>
			/// <returns>
			/// <see cref="List"/> when found.
			/// An empty <see cref="List"/> when not found.
			/// </returns>
			public List GetOrCreateList(string key, Action<List> actionWhenCreate = null) {
				if (ContainsKey(key) && this[key].IsList) {
					return this[key].AsList();
				}
				List list = new List();
				this[key] = list;
				actionWhenCreate?.Invoke(list);
				return list;
			}

			/// <summary>
			/// Get the current Dictionary as String.
			/// </summary>
			/// <returns>Current Dictionary as String</returns>
			public override string ToString() => RawValue.ToString();

			#region IDictionary interface
			public ComplexValue this[string key] {
				get => ContainsKey(key) ? RawValue[key] : Null;
				set => RawValue[key] = value;
			}
			public ICollection<string> Keys => RawValue.Keys;
			public ICollection<ComplexValue> Values => RawValue.Values;
			public int Count => RawValue.Count;
			public bool IsReadOnly => false;
			public void Add(string key, ComplexValue value) => RawValue.Add(key, value);
			public void Add(KeyValuePair<string, ComplexValue> item) => Add(item.Key, item.Value);
			public void Clear() => RawValue.Clear();
			public bool ContainsKey(string key) => RawValue.ContainsKey(key);
			public bool Contains(KeyValuePair<string, ComplexValue> item) => ContainsKey(item.Key) && this[item.Key] == item.Value;
			public bool Remove(string key) => RawValue.Remove(key);
			public bool Remove(KeyValuePair<string, ComplexValue> item) => Contains(item) ? Remove(item.Key) : false;
			public void CopyTo(KeyValuePair<string, ComplexValue>[] array, int arrayIndex) => ((IDictionary<string, ComplexValue>)RawValue).CopyTo(array, arrayIndex);
			public IEnumerator<KeyValuePair<string, ComplexValue>> GetEnumerator() => RawValue.GetEnumerator();
			IEnumerator IEnumerable.GetEnumerator() => RawValue.GetEnumerator();
			public bool TryGetValue(string key, out ComplexValue value) {
				value = this[key];
				return ContainsKey(key);
			}
			#endregion
		}
	}
}